﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace EaWebService
{
    /// <summary>
    /// Class for Generation - representation UnitInt
    /// </summary>
    class Generation
    {
        private Population population;
        private List<UnitInt> newpopulation;

        //For maze problem
        private int[][] maze;
        private int mazeDimension;
        private int exitX;
        private int exitY;
        private int startX;
        private int startY;

        //For population
        private int chromosomeLength;
        private int populationSize;

        //For genetic operation types
        private string mutationType;
        private string selectionType;
        private string crossoverType;
        private string fitnessScaleType;
        private double probability;

        //For scaling
        private double sigma;
        private double boltzmannTemp;

        //Solution
        private int[] solution = null;
        public int[] Solution
        {
            get { return solution; }
        }

        public Generation(int[][] maze,
                    int mazeDimension,
                    int exitX,
                    int exitY,
                    int startX,
                    int startY,
                    int chromosomeLength,
                    int populationSize,
                    string mutationType,
                    string selectionType,
                    string crossoverType,
                    string fitnessScaleType,
                    double probability,
                    double boltzmannTemp = 50)
        {
            this.population = new Population();
            this.newpopulation = new List<UnitInt>();
            this.maze = maze;
            this.mazeDimension = mazeDimension;
            this.exitX = exitX;
            this.exitY = exitY;
            this.startX = startX;
            this.startY = startY;
            this.chromosomeLength = chromosomeLength;
            this.populationSize = populationSize;
            this.mutationType = mutationType;
            this.selectionType = selectionType;
            this.crossoverType = crossoverType;
            this.fitnessScaleType = fitnessScaleType;
            this.probability = probability;
            this.boltzmannTemp = boltzmannTemp;
        }



        //For generating new population 
        public void Epoch()
        {
            if (population.PopulationList.Count == 0)
            {
                GenerateRandomPopulation();
                CalculateFitnessScores(population);
                population.CalculateBestWorstAverageTotalFitness();
                //FitnessScale();
            }

            //Crossover
            while (newpopulation.Count != population.Size)
            {
                UnitInt father = UnitSelection();
                UnitInt mother = UnitSelection();
                UnitCrossover(father, mother, probability, newpopulation);
            }
            //Mutation
            Mutation(newpopulation, probability);

            for (int i = 0; i < newpopulation.Count; i++)
                population.PopulationList.Add(newpopulation[i]);

            CalculateFitnessScores(population);
            if (solution != null)
                return;
            population.CalculateBestWorstAverageTotalFitness();
            //FitnessScale();

            //Sorting population and selecting surviving units
            population.PopulationList.Sort(UnitInt.Compare);
            for (int i = 0; i < newpopulation.Count; i++)
                population.PopulationList.RemoveAt(0);

            newpopulation.Clear();
            population.CalculateBestWorstAverageTotalFitness();
            //FitnessScale();
        }

        //Calculates fitness score using Manhattan distance
        private void CalculateFitnessScores(Population population)
        {
            for (int j = 0; j < population.PopulationList.Count; j++)
            {
                int[] genCode = population.PopulationList[j].GenCode;
                int posX = startX;
                int posY = startY;

                int differenceX=mazeDimension;
                int differenceY=mazeDimension;

                for (int i = 0; i < genCode.Length; i++)
                {
                    bool stop = false;
                    switch (genCode[i])
                    {
                        // west
                        case 1:
                            if (posX > 0 && maze[posY][posX - 1] == 0)
                                posX--;
                            else
                                stop = true;
                            break;

                        // north
                        case 2:
                            if (posY > 0 && maze[posY - 1][posX] == 0)
                                posY--;
                            else
                                stop = true;
                            break;

                        // east
                        case 3:
                            if (posX + 1 < mazeDimension && maze[posY][posX + 1] == 0)
                                posX++;
                            else
                                stop = true;
                            break;

                        // south
                        case 4:
                            if (posY +1 < mazeDimension && maze[posY + 1][posX] == 0)
                                posY++;
                            else
                                stop = true;
                            break;
                    }

                    differenceX = exitX - posX;
                    differenceY = exitY - posY;

                    if (differenceX == 0 && differenceY == 0)
                    {
                        solution = population.PopulationList[j].GenCode;
                    }

                    if (stop == true)
                        break;
                }

                population.PopulationList[j].FitnessValue = 1.0 / (differenceX + differenceY + 1);

            }
        }

        //Generates random population - representation UnitInt
        private void GenerateRandomPopulation()
        {
            Random rand = new Random();
            for (int i = 0; i < populationSize; i++)
            {
                int[] chromosome = new int[chromosomeLength];
                for (int j = 0; j < chromosomeLength; j++)
                    chromosome[j] = 1 + rand.Next(4);
                UnitInt unit = new UnitInt(chromosomeLength, chromosome);
                population.PopulationList.Add(unit);
                population.Size = population.Size + 1;
            }
        }

        //Selecting Mutation type
        private void Mutation(List<UnitInt> newpopulation, double probability)
        {
            Random rand = new Random();
            double chance;
            switch (mutationType)
            {
                case "Random":
                    for (int i = 0; i < newpopulation.Count; i++)
                    {
                        chance = rand.NextDouble();
                        if (chance < probability)
                            newpopulation[i].MutationRandom();
                    }
                    break;
                case "Exchange":
                    for (int i = 0; i < newpopulation.Count; i++)
                    {
                        chance = rand.NextDouble();
                        if (chance < probability)
                            newpopulation[i].MutationExchange();
                    }
                    break;
                case "Displacement":
                    for (int i = 0; i < newpopulation.Count; i++)
                    {
                        chance = rand.NextDouble();
                        if (chance < probability)
                            newpopulation[i].MutationDisplacement();
                    }
                    break;
                case "Scramble":
                    for (int i = 0; i < newpopulation.Count; i++)
                    {
                        chance = rand.NextDouble();
                        if (chance < probability)
                            newpopulation[i].MutationScramble();
                    }
                    break;
                case "Insertion":
                    for (int i = 0; i < newpopulation.Count; i++)
                    {
                        chance = rand.NextDouble();
                        if (chance < probability)
                            newpopulation[i].MutationInsertion();
                    }
                    break;
                case "Inversion":
                    for (int i = 0; i < newpopulation.Count; i++)
                    {
                        chance = rand.NextDouble();
                        if (chance < probability)
                            newpopulation[i].MutationInversion();
                    }
                    break;
                case "DisplacedInversion":
                    for (int i = 0; i < newpopulation.Count; i++)
                    {
                        chance = rand.NextDouble();
                        if (chance < probability)
                            newpopulation[i].MutationDisplacedInversion();
                    }
                    break;
                default:
                    throw new Exception("Unknown Mutation Type");
            }
        }

        //Selecting Crossover type
        private void UnitCrossover(UnitInt father, UnitInt mother, double probability, List<UnitInt> newpopulation)
        {
            switch (crossoverType)
            {
                case "OnePoint":
                    population.CrossoverOnePoint(father, mother, newpopulation);
                    break;
                case "TwoPoints":
                    population.CrossoverTwoPoints(father, mother, newpopulation);
                    break;
                case "Uniform":
                    population.CrossoverUniform(father, mother, probability, newpopulation);
                    break;
                case "HalfUniform":
                    population.CrossoverHalfUniform(father, mother, probability, newpopulation);
                    break;
                default:
                    throw new Exception("Unknown Crossover Type");
            }
        }

        //Selecting Selection type
        private UnitInt UnitSelection()
        {
            switch (selectionType)
            {
                case "RouletteWheel":
                    return population.SelectionRouletteWheel();
                case "Tournament":
                    return population.SelectionTournament();
                case "AlternateTournament":
                    return population.SelectionAlternateTournament();
                default:
                    throw new Exception("Unknown Selection Type");
            }
        }

        // Selecting Fitness scale type
        private void FitnessScale()
        {
            switch (fitnessScaleType)
            {
                case "Rank":
                    population.FitnessScaleRank();
                    break;
                case "Sigma":
                    population.Sigma = sigma;
                    population.FitnessScaleSigma();
                    break;
                case "Boltzmann":
                    population.BoltzmannTemp = boltzmannTemp;
                    population.FitnessScaleBoltzmann();
                    break;
                default:
                    throw new Exception("Unknown Fitness Scale Type");
            }
        }

        //Returns currently best solution
        internal int[] getBestPath()
        {
            double max = population.PopulationList[0].FitnessValue;
            int index = 0;
            for (int i = 1; i < population.PopulationList.Count; i++)
                if (max < population.PopulationList[i].FitnessValue)
                {
                    max = population.PopulationList[i].FitnessValue;
                    index = i;
                }

            return population.PopulationList[index].GenCode;
        }

        //Returns sigma from population
        internal double getSigma()
        {
            return population.Sigma;
        }

    }
}